#pragma once
#include "Trie.hpp"
#include "Log.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <fstream>
#include <algorithm>
#include <mutex>
#include "utility.hpp"
using namespace std;

namespace search_index
{
    // 定义文档的结构化信息
    struct DocInfo
    {
        string title;
        string content;
        string url;
        uint64_t doc_id;
    };

    // 定义映射的Value值，方便倒排索引
    struct InvertedElem
    {
        InvertedElem() : weight(0)
        {
        }
        uint64_t doc_id;
        string word;
        int weight;
    };

    class Index
    {
    private:
        // 一些必要的构造析构函数
        Index() = default;
        Index(const Index &) = delete;
        Index &operator=(const Index &) = delete;

    public:
        ~Index()
        {
        }
        // 单例模式
        static Index *GetInstance()
        {
            if (instance == nullptr)
            {
                mtx.lock();
                if (instance == nullptr)
                    instance = new Index();
                mtx.unlock();
            }
            return instance;
        }
        // 建立正排索引和倒排索引
        bool BulidIndex(const string &input)
        {
            // 打开待读取的文件
            ifstream in(input, ios::in | ios::binary);
            if (!in.is_open())
            {
                lg(Fatal, "open %s fail!", input.c_str());
                return false;
            }
            // 把文件中的信息按行读取出来
            string line;
            int count = 0;
            while (getline(in, line))
            {
                // 建立正排索引
                DocInfo *doc = BulidForwardIndex(line);
                if (doc == nullptr)
                {
                    lg(Warning, "build %s error!", line.c_str());
                    continue;
                }
                // 建立倒排索引
                BulidInvertedIndex(*doc);
                count++;
                if(count % 100 == 0)
                    lg(Info, "当前的已经建立的索引文档: %s", to_string(count).c_str());
            }
            return true;
        }
        // 根据doc_id寻找文档内容
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            // 直接从数组中获取信息
            if (doc_id >= forward_index.size())
            {
                lg(Fatal, "%d is out of range!", &doc_id);
                return nullptr;
            }
            return &forward_index[doc_id];
        }
        // 根据string关键字获得倒排拉链
        vector<InvertedElem> *GetInvertedList(const string &word)
        {
            // 直接从哈希表中查找信息即可
            auto iter = inverted_index.find(word);
            if (iter == inverted_index.end())
            {
                lg(Warning, "%s have no index!", word.c_str());
                return nullptr;
            }
            return &(iter->second);
        }

    private:
        DocInfo *BulidForwardIndex(const string &line)
        {
            // 1. 解析line字符串，进行切分
            vector<string> res;
            const string sep = "\3";
            search_utility::StringUtil::Split(line, &res, sep);
            if (res.size() != 3)
                return nullptr;
            // 2. 填充到DocInfo当中
            DocInfo doc;
            doc.title = res[0];
            doc.content = res[1];
            doc.url = res[2];
            doc.doc_id = forward_index.size();
            // 3. 插入到正排索引中
            forward_index.push_back(move(doc));
            return &(forward_index.back());
        }

        bool allletters(const string& str)
        {
            for(auto& e : str)
                if(e < 'a' || e > 'z')
                    return false;
            return true;
        }

        // 根据文档中的信息建立倒排拉链
        bool BulidInvertedIndex(const DocInfo &doc)
        {
            struct word_cnt
            {
                word_cnt() : title_cnt(0), content_cnt(0)
                {
                }
                int title_cnt;
                int content_cnt;
            };
            // 建立一个用来暂存词频的映射表
            unordered_map<string, word_cnt> word_map;
            // 对于标题进行分词
            vector<string> title_words;
            search_utility::JiebaUtil::CutString(doc.title, &title_words);
            // 对于标题进行词频统计
            for (auto s : title_words)
            {
                transform(s.begin(), s.end(), s.begin(), ::tolower);
                word_map[s].title_cnt++;
            }
            // 对于内容进行分词
            vector<string> content_words;
            search_utility::JiebaUtil::CutString(doc.content, &content_words);
            Trie& tie = Trie::getInstance();
            // 对于内容进行词频统计
            for (auto s : content_words)
            {
                transform(s.begin(), s.end(), s.begin(), ::tolower);
                word_map[s].content_cnt++;
                if(s.size() < 5)
                    continue;
                // 把分词加入到字典树中
                if(allletters(s))
                    tie.insert(s);
            }

            // 建立倒排索引
            for (auto &kv : word_map)
            {
                string key_string = kv.first;
                auto word_count = kv.second;
                // 把倒排索引的信息填充到结构体中
                InvertedElem item;
                item.doc_id = doc.doc_id;
                item.word = key_string;
                item.weight = GetWeight(word_count.title_cnt, word_count.content_cnt);
                // 提取出当前关键字对应的倒排信息，然后把当前词的倒排信息插入进去
                vector<InvertedElem> &inverted_list = inverted_index[key_string];
                inverted_list.push_back(move(item));
            }

            return true;
        }

        // 设计一个计算权值的函数，这里暂时不过多设计
        int GetWeight(int title, int content)
        {
            return title * 10 + content;
        }

    private:
        // 正排索引可以直接使用数组下标当做是文档ID
        vector<DocInfo> forward_index;
        // 倒排索引是一个关键组对应多个文档ID等信息
        unordered_map<string, vector<InvertedElem>> inverted_index;
        static Index *instance;
        static mutex mtx;
    };

    Index *Index::instance = nullptr;
    mutex Index::mtx;
}